Skip to content

Add Prompt Studio promotion client#14

Open
Deepak-Kesavan wants to merge 4 commits intomainfrom
feat/prompt-studio-promotion-client
Open

Add Prompt Studio promotion client#14
Deepak-Kesavan wants to merge 4 commits intomainfrom
feat/prompt-studio-promotion-client

Conversation

@Deepak-Kesavan
Copy link

@Deepak-Kesavan Deepak-Kesavan commented Mar 19, 2026

Summary

New unstract.prompt_studio package with PromptStudioClient for automating Prompt Studio project promotion across environments via Platform API Key (Bearer token) auth.

Methods

Method Description
list_projects() List all projects in org
get_project(tool_id) Get project details
export_project(tool_id) Export project JSON (project-transfer)
import_project(export_data, adapters) Import as new project
sync_prompts(tool_id, export_data, create_copy) Sync prompts into existing project
create_profile(tool_id, ...) Create profile with adapter IDs (falls back to default triad)
export_tool(tool_id) Force export tool for deployment
upload_file(tool_id, file_path) Upload document to project
check_deployment_usage(tool_id) Check if project is deployed
get_default_triad() Get default adapter triad
promote(tool_id, target, ...) Promotion: export → sync → optional deploy export

Usage

One-time setup on target:

from unstract.prompt_studio import PromptStudioClient

target = PromptStudioClient(base_url="https://prod.unstract.com", api_key="<key>", org_id="org_prod")

# Import project and configure profile
result = target.import_project(export_data, adapters={...})
target.create_profile(result["tool_id"])

Ongoing promotion:

source = PromptStudioClient(base_url="https://dev.unstract.com", api_key="<key>", org_id="org_dev")

result = source.promote(
    tool_id="<source-tool-id>",
    target=target,
    target_tool_id="<existing-target-tool-id>",
    create_copy=True,
    export=True,
)

Test plan

  • 14 unit tests covering all methods and promotion flows
  • E2E tested: local → globe staging (export, import, create_profile, sync, export_tool)

New package `unstract.prompt_studio` with `PromptStudioClient` for
automating Prompt Studio project promotion across environments using
Platform API Key (Bearer token) authentication.

Core methods:
- list_projects, get_project, export_project, import_project
- sync_prompts, create_profile, export_tool, upload_file
- check_deployment_usage, get_default_triad

High-level orchestration:
- promote(): export → import/sync → optional export for deployment

create_profile falls back to user's default triad when adapter IDs
are not explicitly provided.

Includes 15 unit tests covering all methods and promotion flows.
@greptile-apps
Copy link

greptile-apps bot commented Mar 19, 2026

Greptile Summary

This PR introduces a new unstract.prompt_studio package providing a PromptStudioClient for automating Prompt Studio project promotion across environments (export, import, sync, deploy) via Bearer token authentication. The implementation is clean and well-documented, and all issues raised in the previous review round (file handle leaks, header dict mutation, PromptStudioClientError not exported, hardcoded is_default, and the promote silent-skip on missing tool_id) have been addressed.

Key changes:

  • src/unstract/prompt_studio/client.py: New client with 10 public methods covering the full promotion lifecycle
  • src/unstract/prompt_studio/__init__.py: Package init exporting both PromptStudioClient and PromptStudioClientError
  • tests/test_prompt_studio.py: 14 unit tests covering core flows

Remaining issues:

  • import os on line 36 of client.py is unused and can be removed
  • Five public methods (get_project, check_deployment_usage, upload_file, get_default_triad, create_profile) have no test coverage; create_profile in particular has non-trivial fallback logic worth testing
  • TestPromote tests use a single shared requests.request mock for both source and target clients; adding URL-level assertions would make call ordering explicit and prevent silent test drift

Confidence Score: 4/5

  • Safe to merge — no logic bugs or security issues; remaining items are a trivial unused import and test coverage gaps.
  • All critical issues from the previous review round are resolved. The implementation is straightforward HTTP wrapping with solid error propagation. The only remaining concerns are stylistic: one unused import and incomplete unit test coverage for roughly half the public API surface (5 of 10 methods). Neither blocks correctness in production.
  • tests/test_prompt_studio.py — missing coverage for create_profile, upload_file, get_default_triad, check_deployment_usage, and get_project.

Important Files Changed

Filename Overview
src/unstract/prompt_studio/client.py Core client implementation. Well-structured with good error handling and docstrings. Previously flagged issues (file handle leaks, header mutation, is_default hardcoding) have been addressed. One unused import os remains on line 36.
src/unstract/prompt_studio/init.py Clean package init. Exports both PromptStudioClient and PromptStudioClientError with explicit re-export aliases and __all__ — previously flagged missing export of the error class is resolved.
tests/test_prompt_studio.py 14 unit tests covering main flows, but five public methods (get_project, check_deployment_usage, upload_file, get_default_triad, create_profile) have no test coverage. Promote tests use a single shared mock for both source and target clients without URL-level assertions.

Sequence Diagram

sequenceDiagram
    participant U as User
    participant S as PromptStudioClient (source)
    participant T as PromptStudioClient (target)
    participant SA as Source API
    participant TA as Target API

    Note over U,TA: One-time setup (import_project + create_profile)
    U->>T: import_project(export_data, adapters)
    T->>TA: POST /prompt-studio/project-transfer/
    TA-->>T: {tool_id, needs_adapter_config}
    T-->>U: result

    U->>T: create_profile(tool_id, llm, ...)
    T->>TA: GET /adapter/default_triad/ (if any adapter missing)
    TA-->>T: defaults
    T->>TA: POST /prompt-studio/profilemanager/{tool_id}
    TA-->>T: profile

    Note over U,TA: Ongoing promotion via promote()
    U->>S: promote(tool_id, target, target_tool_id, export=True)
    S->>SA: GET /prompt-studio/project-transfer/{tool_id}
    SA-->>S: export_data
    S->>T: sync_prompts(target_tool_id, export_data)
    T->>TA: POST /prompt-studio/{target_tool_id}/sync-prompts/
    TA-->>T: {prompts_created, prompts_deleted, ...}
    T-->>S: sync result
    S->>T: export_tool(target_tool_id) [if export=True]
    T->>TA: POST /prompt-studio/export/{target_tool_id}
    TA-->>T: export_result
    S-->>U: {tool_id, prompts_created, export_result}
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/unstract/prompt_studio/client.py
Line: 36

Comment:
**Unused `os` import**

`os` is imported but never referenced anywhere in this file. All file existence checks and path operations are done via `pathlib.Path`. Remove it to avoid linter warnings.

```suggestion
import logging
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: tests/test_prompt_studio.py
Line: 244-279

Comment:
**Single mock intercepts both source and target clients**

`@patch("unstract.prompt_studio.client.requests.request")` patches the module-level `requests.request` globally, so all calls — both the `source.export_project()` call and the `target.sync_prompts()` call — route through the same `mock_request`. The test happens to work because the `side_effect` list is ordered to match call order, but the assertions don't verify *which* client made which call. If the internal call order inside `promote` ever changes (e.g., a pre-flight check is added), the `side_effect` list silently desynchronises and the test could pass while testing the wrong thing.

Consider verifying the URL of each call to make the ordering assertions explicit:
```python
export_call_url = mock_request.call_args_list[0].args[1]
assert "project-transfer" in export_call_url

sync_call_url = mock_request.call_args_list[1].args[1]
assert "sync-prompts" in sync_call_url
```

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: tests/test_prompt_studio.py
Line: 1-10

Comment:
**No tests for five public API methods**

The following public methods on `PromptStudioClient` have zero test coverage:

- `get_project(tool_id)`
- `check_deployment_usage(tool_id)`
- `upload_file(tool_id, file_path)`
- `get_default_triad()`
- `create_profile(tool_id, ...)`

`create_profile` in particular has non-trivial logic (conditional `get_default_triad()` fallback, missing-adapter validation, and the `is_default` flag that was recently added). A test covering at least the fully-explicit-adapters path and the fallback-to-default-triad path would help prevent regressions.

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: "Fix header merge ord..."

promote() now requires target_tool_id — it only syncs into an existing
project. Fresh import is a separate one-time setup step via
import_project() + create_profile().
…ault

- Fix file handle leaks in import_project and upload_file by reading
  eagerly into bytes instead of passing open file handles
- Export PromptStudioClientError from package __init__
- Fix header dict mutation in _request by merging into a new dict
- Expose is_default parameter in create_profile (default True)
- Remove unused io import
- Let caller-supplied headers override defaults (auth as base, not top)
- Raise FileNotFoundError for Path objects that don't exist instead of
  falling through to generic type error
- Separate Path vs str-as-path handling for clarity
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants